1//////////////////////////////////////////////////////////////////////
2// LibFile: bottlecaps.scad
3// Bottle caps and necks for PCO18XX standard plastic beverage bottles, and SPI standard bottle necks.
4// Includes:
5// include <BOSL2/std.scad>
6// include <BOSL2/bottlecaps.scad>
7// FileGroup: Threaded Parts
8// FileSummary: Standard bottle caps and necks.
9//////////////////////////////////////////////////////////////////////
10
11
12include <threading.scad>
13include <structs.scad>
14include <rounding.scad>
15
16// Section: PCO-1810 Bottle Threading
17
18
19// Module: pco1810_neck()
20// Synopsis: Creates a neck for a PCO1810 standard bottle.
21// SynTags: Geom
22// Topics: Bottles, Threading
23// See Also: pco1810_cap()
24// Usage:
25// pco1810_neck([wall]) [ATTACHMENTS];
26// Description:
27// Creates an approximation of a standard PCO-1810 threaded beverage bottle neck.
28// Arguments:
29// wall = Wall thickness in mm.
30// ---
31// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
32// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
33// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
34// Named Anchors:
35// "tamper-ring" = Centered at the top of the anti-tamper ring channel.
36// "support-ring" = Centered at the bottom of the support ring.
37// Example:
38// pco1810_neck();
39// Example: Standard Anchors
40// pco1810_neck() show_anchors(custom=false);
41// Example: Custom Named Anchors
42// expose_anchors(0.3)
43// pco1810_neck()
44// show_anchors(std=false);
45module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
46{
47 inner_d = 21.74;
48 neck_d = 26.19;
49 neck_h = 5.00;
50 support_d = 33.00;
51 support_width = 1.45;
52 support_rad = 0.40;
53 support_h = 21.00;
54 support_ang = 16;
55 tamper_ring_d = 27.97;
56 tamper_ring_width = 0.50;
57 tamper_ring_r = 1.60;
58 tamper_base_d = 25.71;
59 tamper_base_h = 14.10;
60 threadbase_d = 24.51;
61 thread_pitch = 3.18;
62 flank_angle = 20;
63 thread_od = 27.43;
64 lip_d = 25.07;
65 lip_h = 1.70;
66 lip_leadin_r = 0.20;
67 lip_recess_d = 24.94;
68 lip_recess_h = 1.00;
69 lip_roundover_r = 0.58;
70
71 $fn = segs(support_d/2);
72 h = support_h+neck_h;
73 thread_h = (thread_od-threadbase_d)/2;
74 anchors = [
75 named_anchor("support-ring", [0,0,neck_h-h/2]),
76 named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
77 ];
78 attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
79 down(h/2) {
80 rotate_extrude(convexity=10) {
81 polygon(turtle(
82 state=[inner_d/2,0], [
83 "untilx", neck_d/2,
84 "left", 90,
85 "move", neck_h - 1,
86 "arcright", 1, 90,
87 "untilx", support_d/2-support_rad,
88 "arcleft", support_rad, 90,
89 "move", support_width,
90 "arcleft", support_rad, 90-support_ang,
91 "untilx", tamper_base_d/2,
92 "right", 90-support_ang,
93 "untily", h-tamper_base_h, // Tamper ring holder base.
94 "right", 90,
95 "untilx", tamper_ring_d/2,
96 "left", 90,
97 "move", tamper_ring_width,
98 "arcleft", tamper_ring_r, 90,
99 "untilx", threadbase_d/2,
100 "right", 90,
101 "untily", h-lip_h-lip_leadin_r, // Lip base.
102 "arcright", lip_leadin_r, 90,
103 "untilx", lip_d/2,
104 "left", 90,
105 "untily", h-lip_recess_h,
106 "left", 90,
107 "untilx", lip_recess_d/2,
108 "right", 90,
109 "untily", h-lip_roundover_r,
110 "arcleft", lip_roundover_r, 90,
111 "untilx", inner_d/2
112 ]
113 ));
114 }
115 up(h-lip_h) {
116 bottom_half() {
117 difference() {
118 thread_helix(
119 d=threadbase_d-0.1,
120 pitch=thread_pitch,
121 thread_depth=thread_h+0.1,
122 flank_angle=flank_angle,
123 turns=810/360,
124 lead_in=-thread_h*2,
125 anchor=TOP
126 );
127 zrot_copies(rots=[90,270]) {
128 zrot_copies(rots=[-28,28], r=threadbase_d/2) {
129 prismoid([20,1.82], [20,1.82+2*sin(29)*thread_h], h=thread_h+0.1, anchor=BOT, orient=RIGHT);
130 }
131 }
132 }
133 }
134 }
135 }
136 children();
137 }
138}
139
140function pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP) =
141 no_function("pco1810_neck");
142
143
144// Module: pco1810_cap()
145// Synopsis: Creates a cap for a PCO1810 standard bottle.
146// SynTags: Geom
147// Topics: Bottles, Threading
148// See Also: pco1810_neck()
149// Usage:
150// pco1810_cap([h], [r|d], [wall], [texture]) [ATTACHMENTS];
151// Description:
152// Creates a basic cap for a PCO1810 threaded beverage bottle.
153// Arguments:
154// h = The height of the cap.
155// r = Outer radius of the cap.
156// d = Outer diameter of the cap.
157// wall = Wall thickness in mm.
158// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
159// ---
160// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
161// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
162// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
163// Named Anchors:
164// "inside-top" = Centered on the inside top of the cap.
165// Examples:
166// pco1810_cap();
167// pco1810_cap(texture="knurled");
168// pco1810_cap(texture="ribbed");
169// Example: Standard Anchors
170// pco1810_cap(texture="ribbed") show_anchors(custom=false);
171// Example: Custom Named Anchors
172// expose_anchors(0.3)
173// pco1810_cap(texture="ribbed")
174// show_anchors(std=false);
175module pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient=UP)
176{
177 cap_id = 28.58;
178 tamper_ring_h = 14.10;
179 thread_pitch = 3.18;
180 flank_angle = 20;
181 thread_od = cap_id;
182 thread_depth = 1.6;
183
184 rr = get_radius(r=r, d=d, dflt=undef);
185 wwall = default(u_sub(rr,cap_id/2), default(wall, 2));
186 hh = default(h, tamper_ring_h + wwall);
187 checks =
188 assert(wwall >= 0, "wall can't be negative.")
189 assert(hh >= tamper_ring_h, str("height can't be less than ", tamper_ring_h, "."));
190
191 $fn = segs(33/2);
192 w = cap_id + 2*wwall;
193 anchors = [
194 named_anchor("inside-top", [0,0,-(hh/2-wwall)])
195 ];
196 attachable(anchor,spin,orient, d=w, l=hh, anchors=anchors) {
197 down(hh/2) zrot(45) {
198 difference() {
199 union() {
200 if (texture == "knurled") {
201 cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
202 } else if (texture == "ribbed") {
203 cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
204 } else {
205 cyl(d=w, l=hh, anchor=BOTTOM);
206 }
207 }
208 up(hh-tamper_ring_h) cyl(d=cap_id, h=tamper_ring_h+wwall, anchor=BOTTOM);
209 }
210 up(hh-tamper_ring_h+2) thread_helix(d=thread_od-thread_depth*2, pitch=thread_pitch, thread_depth=thread_depth, flank_angle=flank_angle, turns=810/360, lead_in=-thread_depth, internal=true, anchor=BOTTOM);
211 }
212 children();
213 }
214}
215
216function pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient=UP) =
217 no_function("pco1810_cap");
218
219
220
221// Section: PCO-1881 Bottle Threading
222
223
224// Module: pco1881_neck()
225// Synopsis: Creates a neck for a PCO1881 standard bottle.
226// SynTags: Geom
227// Topics: Bottles, Threading
228// See Also: pco1881_cap()
229// Usage:
230// pco1881_neck([wall]) [ATTACHMENTS];
231// Description:
232// Creates an approximation of a standard PCO-1881 threaded beverage bottle neck.
233// Arguments:
234// wall = Wall thickness in mm.
235// ---
236// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
237// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
238// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
239// Named Anchors:
240// "tamper-ring" = Centered at the top of the anti-tamper ring channel.
241// "support-ring" = Centered at the bottom of the support ring.
242// Example:
243// pco1881_neck();
244// Example:
245// pco1881_neck() show_anchors(custom=false);
246// Example:
247// expose_anchors(0.3)
248// pco1881_neck()
249// show_anchors(std=false);
250module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
251{
252 inner_d = 21.74;
253 neck_d = 26.19;
254 neck_h = 5.00;
255 support_d = 33.00;
256 support_width = 0.58;
257 support_rad = 0.30;
258 support_h = 17.00;
259 support_ang = 15;
260 tamper_ring_d = 28.00;
261 tamper_ring_width = 0.30;
262 tamper_ring_ang = 45;
263 tamper_base_d = 25.71;
264 tamper_base_h = 11.20;
265 tamper_divot_r = 1.08;
266 threadbase_d = 24.20;
267 thread_pitch = 2.70;
268 flank_angle = 15;
269 thread_od = 27.4;
270 lip_d = 25.07;
271 lip_h = 1.70;
272 lip_leadin_r = 0.30;
273 lip_recess_d = 24.94;
274 lip_recess_h = 1.00;
275 lip_roundover_r = 0.58;
276
277 $fn = segs(support_d/2);
278 h = support_h+neck_h;
279 thread_h = (thread_od-threadbase_d)/2;
280 anchors = [
281 named_anchor("support-ring", [0,0,neck_h-h/2]),
282 named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
283 ];
284 attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
285 down(h/2) {
286 rotate_extrude(convexity=10) {
287 polygon(turtle(
288 state=[inner_d/2,0], [
289 "untilx", neck_d/2,
290 "left", 90,
291 "move", neck_h - 1,
292 "arcright", 1, 90,
293 "untilx", support_d/2-support_rad,
294 "arcleft", support_rad, 90,
295 "move", support_width,
296 "arcleft", support_rad, 90-support_ang,
297 "untilx", tamper_base_d/2,
298 "arcright", tamper_divot_r, 180-support_ang*2,
299 "left", 90-support_ang,
300 "untily", h-tamper_base_h, // Tamper ring holder base.
301 "right", 90,
302 "untilx", tamper_ring_d/2,
303 "left", 90,
304 "move", tamper_ring_width,
305 "left", tamper_ring_ang,
306 "untilx", threadbase_d/2,
307 "right", tamper_ring_ang,
308 "untily", h-lip_h-lip_leadin_r, // Lip base.
309 "arcright", lip_leadin_r, 90,
310 "untilx", lip_d/2,
311 "left", 90,
312 "untily", h-lip_recess_h,
313 "left", 90,
314 "untilx", lip_recess_d/2,
315 "right", 90,
316 "untily", h-lip_roundover_r,
317 "arcleft", lip_roundover_r, 90,
318 "untilx", inner_d/2
319 ]
320 ));
321 }
322 up(h-lip_h) {
323 difference() {
324 thread_helix(
325 d=threadbase_d-0.1,
326 pitch=thread_pitch,
327 thread_depth=thread_h+0.1,
328 flank_angle=flank_angle,
329 turns=650/360,
330 lead_in=-thread_h*2,
331 anchor=TOP
332 );
333 zrot_copies(rots=[90,270]) {
334 zrot_copies(rots=[-28,28], r=threadbase_d/2) {
335 prismoid([20,1.82], [20,1.82+2*sin(29)*thread_h], h=thread_h+0.1, anchor=BOT, orient=RIGHT);
336 }
337 }
338 }
339 }
340 }
341 children();
342 }
343}
344
345function pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP) =
346 no_function("pco1881_neck");
347
348
349// Module: pco1881_cap()
350// Synopsis: Creates a cap for a PCO1881 standard bottle.
351// SynTags: Geom
352// Topics: Bottles, Threading
353// See Also: pco1881_neck()
354// Usage:
355// pco1881_cap(wall, [texture]) [ATTACHMENTS];
356// Description:
357// Creates a basic cap for a PCO1881 threaded beverage bottle.
358// Arguments:
359// wall = Wall thickness in mm.
360// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
361// ---
362// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
363// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
364// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
365// Named Anchors:
366// "inside-top" = Centered on the inside top of the cap.
367// Examples:
368// pco1881_cap();
369// pco1881_cap(texture="knurled");
370// pco1881_cap(texture="ribbed");
371// Example: Standard Anchors
372// pco1881_cap(texture="ribbed") show_anchors(custom=false);
373// Example: Custom Named Anchors
374// expose_anchors(0.5)
375// pco1881_cap(texture="ribbed")
376// show_anchors(std=false);
377module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
378{
379 $fn = segs(33/2);
380 w = 28.58 + 2*wall;
381 h = 11.2 + wall;
382 anchors = [
383 named_anchor("inside-top", [0,0,-(h/2-wall)])
384 ];
385 attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) {
386 down(h/2) zrot(45) {
387 difference() {
388 union() {
389 if (texture == "knurled") {
390 cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
391 } else if (texture == "ribbed") {
392 cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
393 } else {
394 cyl(d=w, l=11.2+wall, anchor=BOTTOM);
395 }
396 }
397 up(wall) cyl(d=28.58, h=11.2+wall, anchor=BOTTOM);
398 }
399 up(wall+2) thread_helix(d=25.5, pitch=2.7, thread_depth=1.6, flank_angle=15, turns=650/360, lead_in=-1.6, internal=true, anchor=BOTTOM);
400 }
401 children();
402 }
403}
404
405function pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) =
406 no_function("pco1881_cap");
407
408
409
410// Section: Generic Bottle Connectors
411
412// Module: generic_bottle_neck()
413// Synopsis: Creates a generic neck for a bottle.
414// SynTags: Geom
415// Topics: Bottles, Threading
416// See Also: generic_bottle_cap()
417// Usage:
418// generic_bottle_neck([wall], ...) [ATTACHMENTS];
419// Description:
420// Creates a bottle neck given specifications.
421// Arguments:
422// wall = distance between ID and any wall that may be below the support
423// ---
424// neck_d = Outer diameter of neck without threads
425// id = Inner diameter of neck
426// thread_od = Outer diameter of thread
427// height = Height of neck above support
428// support_d = Outer diameter of support ring. Set to 0 for no support.
429// pitch = Thread pitch
430// round_supp = True to round the lower edge of the support ring
431// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
432// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
433// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
434// Named Anchors:
435// "support-ring" = Centered at the bottom of the support ring.
436// Example:
437// generic_bottle_neck();
438module generic_bottle_neck(
439 wall,
440 neck_d = 25,
441 id = 21.4,
442 thread_od = 27.2,
443 height = 17,
444 support_d = 33.0,
445 pitch = 3.2,
446 round_supp = false,
447 anchor = "support-ring",
448 spin = 0,
449 orient = UP
450) {
451 inner_d = id;
452 neck_d = neck_d;
453 supp_d = max(neck_d, support_d);
454 thread_pitch = pitch;
455 flank_angle = 15;
456
457 diamMagMult = neck_d / 26.19;
458 heightMagMult = height / 17.00;
459
460 assert(all_nonnegative([support_d]),"support_d must be a nonnegative number");
461 sup_r = 0.30 * (heightMagMult > 1 ? heightMagMult : 1);
462 support_r = floor(((supp_d == neck_d) ? sup_r : min(sup_r, (supp_d - neck_d) / 2)) * 5000) / 10000;
463 support_rad = (wall == undef || !round_supp) ? support_r :
464 min(support_r, floor((supp_d - (inner_d + 2 * wall)) * 5000) / 10000);
465 //Too small of a radius will cause errors with the arc, this limits granularity to .0001mm
466 support_width = max(heightMagMult,1) * sign(support_d);
467 roundover = 0.58 * diamMagMult;
468 lip_roundover_r = (roundover > (neck_d - inner_d) / 2) ? 0 : roundover;
469 h = height + support_width;
470 echo(h=h);
471 threadbase_d = neck_d - 0.8 * diamMagMult;
472
473 $fn = segs(33 / 2);
474 thread_h = (thread_od - threadbase_d) / 2;
475 anchors = [
476 named_anchor("support-ring", [0, 0, 0 - h / 2])
477 ];
478 attachable(anchor, spin, orient, d = neck_d, l = h, anchors = anchors) {
479 down(h / 2) {
480 rotate_extrude(convexity = 10) {
481 polygon(turtle(
482 state = [inner_d / 2, 0], (supp_d != neck_d) ? [
483 "untilx", supp_d / 2 - ((round_supp) ? support_rad : 0),
484 "arcleft", ((round_supp) ? support_rad : 0), 90,
485 "untily", support_width - support_rad,
486 "arcleft", support_rad, 90,
487 "untilx", neck_d / 2,
488 "right", 90,
489 "untily", h - lip_roundover_r,
490 "arcleft", lip_roundover_r, 90,
491 "untilx", inner_d / 2
492 ] : [
493 "untilx", supp_d / 2 - ((round_supp) ? support_rad : 0),
494 "arcleft", ((round_supp) ? support_rad : 0), 90,
495 "untily", h - lip_roundover_r,
496 "arcleft", lip_roundover_r, 90,
497 "untilx", inner_d / 2
498 ]
499 ));
500 }
501 up(h - pitch / 2 - lip_roundover_r) {
502 difference() {
503 thread_helix(
504 d = threadbase_d - 0.1 * diamMagMult,
505 pitch = thread_pitch,
506 thread_depth = thread_h + 0.1 * diamMagMult,
507 flank_angle = flank_angle,
508 turns = (height - pitch - lip_roundover_r) * .6167 / pitch,
509 lead_in = -thread_h * 2,
510 anchor = TOP
511 );
512 zrot_copies(rots = [90, 270]) {
513 zrot_copies(rots = [-28, 28], r = threadbase_d / 2) {
514 prismoid(
515 [20 * heightMagMult, 1.82 * diamMagMult],
516 [20 * heightMagMult, 1.82 * diamMagMult * .6 + 2 * sin(29) * thread_h],
517 h = thread_h + 0.1 * diamMagMult,
518 anchor = BOT,
519 orient = RIGHT
520 );
521 }
522 }
523 }
524 }
525 }
526 children();
527 }
528}
529
530function generic_bottle_neck(
531 neck_d,
532 id,
533 thread_od,
534 height,
535 support_d,
536 pitch,
537 round_supp,
538 wall,
539 anchor, spin, orient
540) = no_function("generic_bottle_neck");
541
542
543// Module: generic_bottle_cap()
544// Synopsis: Creates a generic cap for a bottle.
545// SynTags: Geom
546// Topics: Bottles, Threading
547// See Also: generic_bottle_neck(), sp_cap()
548// Usage:
549// generic_bottle_cap(wall, [texture], ...) [ATTACHMENTS];
550// Description:
551// Creates a basic threaded cap given specifications. You must give exactly two of `thread_od`, `neck_od` and `thread_depth` to
552// specify the thread geometry. Note that most glass bottles conform to the SPI standard and caps for them may be more easily produced using {{sp_cap()}}.
553// Arguments:
554// wall = Wall thickness. Default: 2
555// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
556// ---
557// height = Interior height of the cap
558// thread_od = Outer diameter of the threads
559// neck_od = Outer diameter of neck
560// thread_depth = Depth of the threads
561// tolerance = Extra space to add to the outer diameter of threads and neck. Applied to radius. Default: 0.2
562// flank_angle = Angle of taper on threads. Default: 15
563// pitch = Thread pitch. Default: 4
564// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
565// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
566// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
567// Named Anchors:
568// "inside-top" = Centered on the inside top of the cap.
569// Examples:
570// generic_bottle_cap(thread_depth=2,neck_od=INCH,height=INCH/2);
571// generic_bottle_cap(texture="knurled",neck_od=25,thread_od=30,height=10);
572// generic_bottle_cap(texture="ribbed",thread_depth=3,thread_od=25,height=13);
573module generic_bottle_cap(
574 wall = 2,
575 texture = "none",
576 height,
577 thread_depth,
578 thread_od,
579 tolerance = .2,
580 neck_od,
581 flank_angle = 15,
582 pitch = 4,
583 anchor = BOTTOM,
584 spin = 0,
585 orient = UP
586) {
587 $fn = segs(33 / 2);
588 dummy = assert(num_defined([thread_od,neck_od,thread_depth])==2, "Must define exactly two of thread_od, neck_od and thread_depth")
589 assert(is_def(thread_depth) || (all_positive([neck_od,thread_od]) && thread_od>neck_od), "thread_od must be larger than neck_od")
590 assert(is_undef(thread_depth) || all_positive([thread_depth,first_defined([neck_od,thread_od])]), "thread_depth, and neck_od/thread_od must be positive");
591 thread_depth = !is_undef(thread_depth) ? thread_depth : (thread_od - neck_od)/2;
592 neck_od = !is_undef(neck_od) ? neck_od : thread_od-2*thread_depth;
593 thread_od = !is_undef(thread_od) ? thread_od : neck_od+2*thread_depth;
594 threadOuterDTol = thread_od + 2*tolerance;
595 w = threadOuterDTol + 2 * wall;
596 h = height + wall;
597 neckOuterDTol = neck_od + 2 * tolerance;
598
599 diamMagMult = (w > 32.58) ? w / 32.58 : 1;
600 heightMagMult = (height > 11.2) ? height / 11.2 : 1;
601
602 anchors = [
603 named_anchor("inside-top", [0, 0, -(h / 2 - wall)])
604 ];
605 attachable(anchor, spin, orient, d = w, l = h, anchors = anchors) {
606 down(h / 2) {
607 difference() {
608 union() {
609 // For the knurled and ribbed caps the PCO caps in BOSL2 cut into the wall
610 // thickness so the wall+texture are the specified wall thickness. That
611 // seems wrong so this does specified thickness+texture
612 if (texture == "knurled")
613 cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
614 else if (texture == "ribbed")
615 cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
616 else
617 cyl(d = w, l = h, anchor = BOTTOM);
618 }
619 up(wall) cyl(d = threadOuterDTol, h = h, anchor = BOTTOM);
620 }
621 up(wall + pitch / 2) {
622 thread_helix(d = neckOuterDTol+.02, pitch = pitch, thread_depth = thread_depth+.01, flank_angle = flank_angle,
623 turns = ((height - pitch) / pitch), lead_in = -thread_depth, internal = true, anchor = BOTTOM);
624 }
625 }
626 children();
627 }
628}
629
630function generic_bottle_cap(
631 wall, texture, height,
632 thread_od, tolerance,
633 neck_od, flank_angle, pitch,
634 anchor, spin, orient
635) = no_function("generic_bottle_cap");
636
637
638// Module: bottle_adapter_neck_to_cap()
639// Synopsis: Creates a generic adaptor between a neck and a cap.
640// SynTags: Geom
641// Topics: Bottles, Threading
642// See Also: bottle_adapter_neck_to_neck()
643// Usage:
644// bottle_adapter_neck_to_cap(wall, [texture], ...) [ATTACHMENTS];
645// Description:
646// Creates a threaded neck to cap adapter
647// Arguments:
648// wall = Thickness of wall between neck and cap when d=0. Leave undefined to have the outside of the tube go from the OD of the neck support ring to the OD of the cap. Default: undef
649// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
650// cap_wall = Wall thickness of the cap in mm.
651// cap_h = Interior height of the cap in mm.
652// cap_thread_depth = Cap thread depth. Default: 2.34
653// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
654// cap_neck_od = Inner diameter of the cap threads.
655// cap_neck_id = Inner diameter of the hole through the cap.
656// cap_thread_taper = Angle of taper on threads.
657// cap_thread_pitch = Thread pitch in mm
658// neck_d = Outer diameter of neck w/o threads
659// neck_id = Inner diameter of neck
660// neck_thread_od = 27.2
661// neck_h = Height of neck down to support ring
662// neck_thread_pitch = Thread pitch in mm.
663// neck_support_od = Outer diameter of neck support ring. Leave undefined to set equal to OD of cap. Set to 0 for no ring. Default: undef
664// d = Distance between bottom of neck and top of cap
665// taper_lead_in = Length to leave straight before tapering on tube between neck and cap if exists.
666// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
667// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
668// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
669// Examples:
670// bottle_adapter_neck_to_cap();
671module bottle_adapter_neck_to_cap(
672 wall,
673 texture = "none",
674 cap_wall = 2,
675 cap_h = 11.2,
676 cap_thread_depth = 2.34,
677 tolerance = .2,
678 cap_neck_od = 25.5,
679 cap_neck_id,
680 cap_thread_taper = 15,
681 cap_thread_pitch = 4,
682 neck_d = 25,
683 neck_id = 21.4,
684 neck_thread_od = 27.2,
685 neck_h = 17,
686 neck_thread_pitch = 3.2,
687 neck_support_od,
688 d = 0,
689 taper_lead_in = 0, anchor, spin,orient
690) {
691 cap_od = cap_neck_od + 2*(cap_thread_depth - 0.8) + 2 * tolerance;
692 neck_support_od = (neck_support_od == undef || (d == 0 && neck_support_od < cap_od)) ? cap_od+2*cap_wall
693 : neck_support_od;
694 cap_neck_id = default(cap_neck_id,neck_id);
695 wall = default(wall, neck_support_od + neck_d + cap_od + neck_id - 2*tolerance);
696 echo(wall=wall);
697
698 $fn = segs(33 / 2);
699 wallt1 = min(wall, (max(neck_support_od, neck_d) - neck_id) / 2);
700 wallt2 = min(wall, (cap_od + 2 * cap_wall - cap_neck_id) / 2);
701
702 top_h = neck_h + max(1,neck_h/17)*sign(neck_support_od);
703 echo(top_h=top_h);
704 bot_h = cap_h + cap_wall;
705 attachable(anchor=anchor,orient=orient,spin=spin, r=max([neck_id/2+wallt1, cap_neck_id/2+wallt2, neck_support_od/2]), h=top_h+bot_h+d) {
706 zmove((bot_h-top_h)/2)
707 difference(){
708 union(){
709 up(d / 2) {
710 generic_bottle_neck(neck_d = neck_d,
711 id = neck_id,
712 thread_od = neck_thread_od,
713 height = neck_h,
714 support_d = neck_support_od,
715 pitch = neck_thread_pitch,
716 round_supp = ((wallt1 < (neck_support_od - neck_id) / 2) && (d > 0 || neck_support_od > (cap_thread_od + 2 * (cap_wall + tolerance)))),
717 wall = (d > 0) ? wallt1 : min(wallt1, ((cap_od + 2 * (cap_wall) - neck_id) / 2))
718 );
719 }
720 if (d != 0) {
721 rotate_extrude(){
722 polygon(points = [
723 [0, d / 2],
724 [neck_id / 2 + wallt1, d / 2],
725 [neck_id / 2 + wallt1, d / 2 - taper_lead_in],
726 [cap_neck_id / 2 + wallt2, taper_lead_in - d / 2],
727 [cap_neck_id / 2 + wallt2, -d / 2],
728 [0, -d / 2]
729 ]);
730 }
731 }
732 down(d / 2){
733 generic_bottle_cap(wall = cap_wall,
734 texture = texture,
735 height = cap_h,
736 thread_depth = cap_thread_depth,
737 tolerance = tolerance,
738 neck_od = cap_neck_od,
739 flank_angle = cap_thread_taper,
740 orient = DOWN,
741 pitch = cap_thread_pitch
742 );
743 }
744 }
745 rotate_extrude() {
746 polygon(points = [
747 [0, d / 2 + 0.1],
748 [neck_id / 2, d / 2],
749 [neck_id / 2, d / 2 - taper_lead_in],
750 [cap_neck_id / 2, taper_lead_in - d / 2],
751 [cap_neck_id / 2, -d / 2 - cap_wall],
752 [0, -d / 2 - cap_wall - 0.1]
753 ]);
754 }
755 }
756 children();
757 }
758}
759
760function bottle_adapter_neck_to_cap(
761 wall, texture, cap_wall, cap_h, cap_thread_depth1,
762 tolerance, cap_neck_od, cap_neck_id, cap_thread_taper,
763 cap_thread_pitch, neck_d, neck_id, neck_thread_od,
764 neck_h, neck_thread_pitch, neck_support_od, d, taper_lead_in
765) = no_fuction("bottle_adapter_neck_to_cap");
766
767
768// Module: bottle_adapter_cap_to_cap()
769// Synopsis: Creates a generic adaptor between a cap and a cap.
770// SynTags: Geom
771// Topics: Bottles, Threading
772// See Also: bottle_adapter_neck_to_cap(), bottle_adapter_neck_to_neck()
773// Usage:
774// bottle_adapter_cap_to_cap(wall, [texture]) [ATTACHMENTS];
775// Description:
776// Creates a threaded cap to cap adapter.
777// Arguments:
778// wall = Wall thickness in mm.
779// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
780// cap_h1 = Interior height of top cap.
781// cap_thread_depth1 = Thread depth on top cap. Default: 2.34
782// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
783// cap_neck_od1 = Inner diameter of threads on top cap.
784// cap_thread_pitch1 = Thread pitch of top cap in mm.
785// cap_h2 = Interior height of bottom cap. Leave undefined to duplicate cap_h1.
786// cap_thread_depth2 = Thread depth on bottom cap. Default: same as cap_thread_depth1
787// cap_neck_od2 = Inner diameter of threads on top cap. Leave undefined to duplicate cap_neck_od1.
788// cap_thread_pitch2 = Thread pitch of bottom cap in mm. Leave undefinced to duplicate cap_thread_pitch1.
789// d = Distance between caps.
790// neck_id1 = Inner diameter of cutout in top cap.
791// neck_id2 = Inner diameter of cutout in bottom cap.
792// taper_lead_in = Length to leave straight before tapering on tube between caps if exists.
793// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
794// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
795// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
796// Examples:
797// bottle_adapter_cap_to_cap();
798module bottle_adapter_cap_to_cap(
799 wall = 2,
800 texture = "none",
801 cap_h1 = 11.2,
802 cap_thread_depth1 = 2.34,
803 tolerance = .2,
804 cap_neck_od1 = 25.5,
805 cap_thread_pitch1 = 4,
806 cap_h2,
807 cap_thread_depth2,
808 cap_neck_od2,
809 cap_thread_pitch2,
810 d = 0,
811 neck_id,
812 taper_lead_in = 0, anchor, spin,orient
813) {
814 cap_h2 = default(cap_h2,cap_h1);
815 cap_thread_depth2 = default(cap_thread_depth2,cap_thread_depth1);
816 cap_neck_od2 = default(cap_neck_od2,cap_neck_od1);
817 cap_thread_pitch2 = default(cap_thread_pitch2,cap_thread_pitch1);
818 taper_lead_in = (d >= taper_lead_in * 2) ? taper_lead_in : d / 2;
819
820 neck_id = min(cap_neck_od1 - cap_thread_depth1, cap_neck_od2-cap_thread_depth2);
821
822 top_h = cap_h1+wall;
823 bot_h = cap_h2+wall;
824
825
826 cap_od1 = cap_neck_od1 + 2*(cap_thread_depth1 - 0.8) + 2 * tolerance; // WTF; Engineered for consistency with old code, but
827 cap_od2 = cap_neck_od2 + 2*(cap_thread_depth2 - 0.8) + 2 * tolerance; // WTF; Engineered for consistency with old code, but
828
829 $fn = segs(33 / 2);
830 attachable(anchor=anchor,spin=spin,orient=orient, h=top_h+bot_h+d, d=max(cap_od1,cap_od2)+2*wall){
831 zmove((bot_h-top_h)/2)
832 difference(){
833 union(){
834 up(d / 2){
835 generic_bottle_cap(
836 orient = UP,
837 wall = wall,
838 texture = texture,
839 height = cap_h1,
840 thread_depth = cap_thread_depth1,
841 tolerance = tolerance,
842 neck_od = cap_neck_od1,
843 pitch = cap_thread_pitch1
844 );
845 }
846 if (d != 0) {
847 rotate_extrude() {
848 polygon(points = [
849 [0, d / 2],
850 [cap_od1 / 2 + wall, d / 2],
851 [cap_od1 / 2 + wall, d / 2 - taper_lead_in],
852 [cap_od2 / 2 + wall, taper_lead_in - d / 2],
853 [cap_od2 / 2 + wall, -d / 2],
854 [0, -d / 2]
855 ]);
856 }
857 }
858 down(d / 2){
859 generic_bottle_cap(
860 orient = DOWN,
861 wall = wall,
862 texture = texture,
863 height = cap_h2,
864 thread_depth = cap_thread_depth2,
865 tolerance = tolerance,
866 neck_od = cap_neck_od2,
867 pitch = cap_thread_pitch2
868 );
869 }
870 }
871 rotate_extrude() {
872 polygon(points = [
873 [0, wall + d / 2 + 0.1],
874 [neck_id / 2, wall + d / 2],
875 [neck_id / 2, wall + d / 2 - taper_lead_in],
876 [neck_id / 2, taper_lead_in - d / 2 - wall],
877 [neck_id / 2, -d / 2 - wall],
878 [0, -d / 2 - wall - 0.1]
879 ]);
880 }
881 }
882 children();
883 }
884}
885
886function bottle_adapter_cap_to_cap(
887 wall, texture, cap_h1, cap_thread_od1, tolerance,
888 cap_neck_od1, cap_thread_pitch1, cap_h2, cap_thread_od2,
889 cap_neck_od2, cap_thread_pitch2, d, neck_id1, neck_id2, taper_lead_in
890) = no_function("bottle_adapter_cap_to_cap");
891
892
893// Module: bottle_adapter_neck_to_neck()
894// Synopsis: Creates a generic adaptor between a neck and a neck.
895// SynTags: Geom
896// Topics: Bottles, Threading
897// See Also: bottle_adapter_neck_to_cap(), bottle_adapter_cap_to_cap()
898// Usage:
899// bottle_adapter_neck_to_neck(...) [ATTACHMENTS];
900// Description:
901// Creates a threaded neck to neck adapter.
902// Arguments:
903// ---
904// d = Distance between bottoms of necks
905// neck_od1 = Outer diameter of top neck w/o threads
906// neck_id1 = Inner diameter of top neck
907// thread_od1 = Outer diameter of threads on top neck
908// height1 = Height of top neck above support ring.
909// support_od1 = Outer diameter of the support ring on the top neck. Set to 0 for no ring.
910// thread_pitch1 = Thread pitch of top neck.
911// neck_od2 = Outer diameter of bottom neck w/o threads. Leave undefined to duplicate neck_od1
912// neck_id2 = Inner diameter of bottom neck. Leave undefined to duplicate neck_id1
913// thread_od2 = Outer diameter of threads on bottom neck. Leave undefined to duplicate thread_od1
914// height2 = Height of bottom neck above support ring. Leave undefined to duplicate height1
915// support_od2 = Outer diameter of the support ring on bottom neck. Set to 0 for no ring. Leave undefined to duplicate support_od1
916// pitch2 = Thread pitch of bottom neck. Leave undefined to duplicate thread_pitch1
917// taper_lead_in = Length to leave straight before tapering on tube between necks if exists.
918// wall = Thickness of tube wall between necks. Leave undefined to match outer diameters with the neckODs/supportODs.
919// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
920// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
921// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
922// Examples:
923// bottle_adapter_neck_to_neck();
924module bottle_adapter_neck_to_neck(
925 d = 0,
926 neck_od1 = 25,
927 neck_id1 = 21.4,
928 thread_od1 = 27.2,
929 height1 = 17,
930 support_od1 = 33.0,
931 thread_pitch1 = 3.2,
932 neck_od2, neck_id2,
933 thread_od2, height2,
934 support_od2, pitch2,
935 taper_lead_in = 0, wall, anchor, spin, orient
936) {
937 neck_od2 = (neck_od2 == undef) ? neck_od1 : neck_od2;
938 neck_id2 = (neck_id2 == undef) ? neck_id1 : neck_id2;
939 thread_od2 = (thread_od2 == undef) ? thread_od1 : thread_od2;
940 height2 = (height2 == undef) ? height1 : height2;
941 support_od2 = (support_od2 == undef) ? support_od1 : support_od2;
942 pitch2 = (pitch2 == undef) ? thread_pitch1 : pitch2;
943 wall = (wall == undef) ? support_od1 + support_od2 + neck_id1 + neck_id2 : wall;
944
945 supprtOD2 = (d == 0 && support_od2 != 0) ? max(neck_od1, support_od2) : support_od2;
946 supprtOD1 = (d == 0 && support_od1 != 0) ? max(neck_od2, support_od1) : support_od1;
947
948 $fn = segs(33 / 2);
949 wallt1 = min(wall, (max(supprtOD1, neck_od1) - neck_id1) / 2);
950 wallt2 = min(wall, (max(supprtOD2, neck_od2) - neck_id2) / 2);
951
952 taper_lead_in = (d >= taper_lead_in * 2) ? taper_lead_in : d / 2;
953
954 top_h = height1 + max(1,height1/17)*sign(support_od1);
955 bot_h = height2 + max(1,height2/17)*sign(support_od2);
956
957 attachable(anchor=anchor,orient=orient,spin=spin, h=top_h+bot_h+d, d=max(neck_od1,neck_od2)){
958 zmove((bot_h-top_h)/2)
959 difference(){
960 union(){
961 up(d / 2){
962 generic_bottle_neck(orient = UP,
963 neck_d = neck_od1,
964 id = neck_id1,
965 thread_od = thread_od1,
966 height = height1,
967 support_d = supprtOD1,
968 pitch = thread_pitch1,
969 round_supp = ((wallt1 < (supprtOD1 - neck_id1) / 2) || (support_od1 > max(neck_od2, support_od2) && d == 0)),
970 wall = (d > 0) ? wallt1 : min(wallt1, ((max(neck_od2, support_od2)) - neck_id1) / 2)
971 );
972 }
973 if (d != 0) {
974 rotate_extrude() {
975 polygon(points = [
976 [0, d / 2],
977 [neck_id1 / 2 + wallt1, d / 2],
978 [neck_id1 / 2 + wallt1, d / 2 - taper_lead_in],
979 [neck_id2 / 2 + wallt2, taper_lead_in - d / 2],
980 [neck_id2 / 2 + wallt2, -d / 2],
981 [0, -d / 2]
982 ]);
983 }
984 }
985 down(d / 2){
986 generic_bottle_neck(orient = DOWN,
987 neck_d = neck_od2,
988 id = neck_id2,
989 thread_od = thread_od2,
990 height = height2,
991 support_d = supprtOD2,
992 pitch = pitch2,
993 round_supp = ((wallt2 < (supprtOD2 - neck_id2) / 2) || (support_od2 > max(neck_od1, support_od1) && d == 0)),
994 wall = (d > 0) ? wallt2 : min(wallt2, ((max(neck_od1, support_od1)) - neck_id2) / 2)
995 );
996 }
997 }
998 if (neck_id1 != undef || neck_id2 != undef) {
999 neck_id1 = (neck_id1 == undef) ? neck_id2 : neck_id1;
1000 neck_id2 = (neck_id2 == undef) ? neck_id1 : neck_id2;
1001
1002 rotate_extrude() {
1003 polygon(points = [
1004 [0, d / 2],
1005 [neck_id1 / 2, d / 2],
1006 [neck_id1 / 2, d / 2 - taper_lead_in],
1007 [neck_id2 / 2, taper_lead_in - d / 2],
1008 [neck_id2 / 2, -d / 2],
1009 [0, -d / 2]
1010 ]);
1011 }
1012 }
1013 }
1014 children();
1015 }
1016}
1017
1018function bottle_adapter_neck_to_neck(
1019 d, neck_od1, neck_id1, thread_od1, height1,
1020 support_od1, thread_pitch1, neck_od2, neck_id2,
1021 thread_od2, height2, support_od2,
1022 pitch2, taper_lead_in, wall
1023) = no_fuction("bottle_adapter_neck_to_neck");
1024
1025
1026
1027// Section: SPI Bottle Threading
1028
1029
1030// Module: sp_neck()
1031// Synopsis: Creates an SPI threaded bottle neck.
1032// SynTags: Geom
1033// Topics: Bottles, Threading
1034// See Also: sp_cap()
1035// Usage:
1036// sp_neck(diam, type, wall|id=, [style=], [bead=]) [ATTACHMENTS];
1037// Description:
1038// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
1039// supply the nominal outer diameter of the threads and the thread type, one of
1040// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
1041// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
1042// You can also choose between the L style thread, which is symmetric and
1043// the M style thread, which is an asymmetric buttress thread. The M style
1044// may be good for 3d printing if printed with the flat face up.
1045// You can specify the wall thickness (measured from the base of the threads) or
1046// the inner diameter, and you can specify an optional bead at the base of the threads.
1047// Arguments:
1048// diam = nominal outer diameter of threads
1049// type = thread type, one of 400, 410 and 415
1050// wall = wall thickness
1051// ---
1052// id = inner diameter
1053// style = Either "L" or "M" to specify the thread style. Default: "L"
1054// bead = if true apply a bad to the neck. Default: false
1055// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1056// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1057// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1058// Examples:
1059// sp_neck(48,400,2);
1060// sp_neck(48,400,2,bead=true);
1061// sp_neck(22,410,2);
1062// sp_neck(22,410,2,bead=true);
1063// sp_neck(28,415,id=20,style="M");
1064// sp_neck(13,415,wall=1,style="M",bead=true);
1065
1066
1067// Thread specs from https://www.isbt.com/threadspecs-downloads.asp
1068
1069// T = peak to peak diameter (outer diameter)
1070// I = Inner diameter
1071// S = space above top thread
1072// H = total height of neck
1073
1074_sp_specs = [
1075 [400, //diam T I H S tpi
1076 [[ 18, [ 17.68, 8.26, 9.42, 0.94, 8]],
1077 [ 20, [ 19.69, 10.26, 9.42, 0.94, 8]],
1078 [ 22, [ 21.69, 12.27, 9.42, 0.94, 8]],
1079 [ 24, [ 23.67, 13.11, 10.16, 1.17, 8]],
1080 [ 28, [ 27.38, 15.60, 10.16, 1.17, 6]],
1081 [ 30, [ 28.37, 16.59, 10.24, 1.17, 6]],
1082 [ 33, [ 31.83, 20.09, 10.24, 1.17, 6]],
1083 [ 35, [ 34.34, 22.23, 10.24, 1.17, 6]],
1084 [ 38, [ 37.19, 25.07, 10.24, 1.17, 6]],
1085 [ 40, [ 39.75, 27.71, 10.24, 1.17, 6]],
1086 [ 43, [ 41.63, 29.59, 10.24, 1.17, 6]],
1087 [ 45, [ 43.82, 31.78, 10.24, 1.17, 6]],
1088 [ 48, [ 47.12, 35.08, 10.24, 1.17, 6]],
1089 [ 51, [ 49.56, 37.57, 10.36, 1.17, 6]],
1090 [ 53, [ 52.07, 40.08, 10.36, 1.17, 6]],
1091 [ 58, [ 56.06, 44.07, 10.36, 1.17, 6]],
1092 [ 60, [ 59.06, 47.07, 10.36, 1.17, 6]],
1093 [ 63, [ 62.08, 50.09, 10.36, 1.17, 6]],
1094 [ 66, [ 65.07, 53.09, 10.36, 1.17, 6]],
1095 [ 70, [ 69.06, 57.07, 10.36, 1.17, 6]],
1096 [ 75, [ 73.56, 61.57, 10.36, 1.17, 6]],
1097 [ 77, [ 76.66, 64.67, 12.37, 1.52, 6]],
1098 [ 83, [ 82.58, 69.93, 12.37, 1.52, 5]],
1099 [ 89, [ 88.75, 74.12, 13.59, 1.52, 5]],
1100 [100, [ 99.57, 84.94, 15.16, 1.52, 5]],
1101 [110, [109.58, 94.92, 15.16, 1.52, 5]],
1102 [120, [119.56,104.93, 17.40, 1.52, 5]],
1103 ]],
1104 [410, //diam T I H S tpi L W
1105 [[ 18, [ 17.68, 8.26, 13.28, 0.94, 8, 9.17, 2.13]],
1106 [ 20, [ 19.59, 10.26, 14.07, 0.94, 8, 9.17, 2.13]],
1107 [ 22, [ 21.69, 12.27, 14.86, 0.94, 8, 9.55, 2.13]],
1108 [ 24, [ 23.67, 13.11, 16.41, 1.17, 8, 11.10, 2.13]],
1109 [ 28, [ 27.38, 15.60, 17.98, 1.17, 6, 11.76, 2.39]],
1110 ]],
1111 [415, //diam T I H S tpi L W
1112 [[ 13, [ 12.90, 5.54, 11.48, 0.94,12, 7.77, 1.14]],
1113 [ 15, [ 14.61, 6.55, 14.15, 0.94,12, 8.84, 1.14]],
1114 [ 18, [ 17.68, 8.26, 15.67, 0.94, 8, 10.90, 2.13]],
1115 [ 20, [ 19.69, 10.26, 18.85, 0.94, 8, 11.58, 2.13]],
1116 [ 22, [ 21.69, 12.27, 21.26, 0.94, 8, 13.87, 2.13]],
1117 [ 24, [ 23.67, 13.11, 24.31, 1.17, 8, 14.25, 2.13]],
1118 [ 28, [ 27.38, 15.60, 27.48, 1.17, 6, 16.64, 2.39]],
1119 [ 33, [ 31.83, 20.09, 32.36, 1.17, 6, 19.61, 2.39]],
1120 ]]
1121];
1122
1123_sp_twist = [ [400, 360],
1124 [410, 540],
1125 [415, 720]
1126 ];
1127
1128
1129// profile data: tpi, total width, depth,
1130_sp_thread_width= [
1131 [5, 3.05],
1132 [6, 2.39],
1133 [8, 2.13],
1134 [12, 1.14], // But note style M is different
1135 ];
1136
1137
1138function _sp_thread_profile(tpi, a, S, style, flip=false) =
1139 let(
1140 pitch = 1/tpi*INCH,
1141 cL = a*(1-1/sqrt(3)),
1142 cM = (1-tan(10))*a/2,
1143 // SP specified roundings for the thread profile have special case for tpi=12
1144 roundings = style=="L" && tpi < 12 ? 0.5
1145 : style=="M" && tpi < 12 ? [0.25, 0.25, 0.75, 0.75]
1146 : style=="L" ? [0.38, 0.13, 0.13, 0.38]
1147 : /* style=="M" */ [0.25, 0.25, 0.2, 0.5],
1148 path1 = style=="L"
1149 ? round_corners([[-1/2*pitch,-a/2],
1150 [-a/2,-a/2],
1151 [-cL/2,0],
1152 [cL/2,0],
1153 [a/2,-a/2],
1154 [1/2*pitch,-a/2]], radius=roundings, closed=false,$fn=24)
1155 : round_corners(
1156 [[-1/2*pitch,-a/2],
1157 [-a/2, -a/2],
1158 [-cM, 0],
1159 [0,0],
1160 [a/2,-a/2],
1161 [1/2*pitch,-a/2]], radius=roundings, closed=false, $fn=24),
1162 path2 = flip ? reverse(xflip(path1)) : path1
1163 )
1164 // Shift so that the profile is S mm from the right end to create proper length S top gap
1165 select(right(-a/2+1/2-S,p=path2),1,-2)/pitch;
1166
1167
1168function sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient) = no_function("sp_neck");
1169module sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient)
1170{
1171 assert(num_defined([wall,id])==1, "Must define exactly one of wall and id");
1172
1173 table = struct_val(_sp_specs,type);
1174 dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
1175 entry = struct_val(table, diam);
1176 dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1177 assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
1178
1179 T = entry[0];
1180 I = entry[1];
1181 H = entry[2];
1182 S = entry[3];
1183 tpi = entry[4];
1184
1185 // a is the width of the thread
1186 a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
1187
1188 twist = struct_val(_sp_twist, type);
1189
1190 profile = _sp_thread_profile(tpi,a,S,style);
1191
1192 depth = a/2;
1193 taperlen = 2*a;
1194
1195 beadmax = type==400 ? (T/2-depth)+depth*1.25
1196 : diam <=15 ? (T-.15)/2 : (T-.05)/2;
1197
1198 W = type==400 ? a*1.5 // arbitrary decision for type 400
1199 : entry[6]; // specified width for 410 and 415
1200
1201 beadpts = [
1202 [0,-W/2],
1203 each arc(16, points = [[T/2-depth, -W/2],
1204 [beadmax, 0],
1205 [T/2-depth, W/2]]),
1206 [0,W/2]
1207 ];
1208
1209 isect400 = [for(seg=pair(beadpts)) let(segisect = line_intersection([[T/2,0],[T/2,1]] , seg, LINE, SEGMENT)) if (is_def(segisect)) segisect.y];
1210
1211 extra_bot = type==400 && bead ? -min(column(beadpts,1))+max(isect400) : 0;
1212 bead_shift = type==400 ? H+max(isect400) : entry[5]+W/2; // entry[5] is L
1213
1214 attachable(anchor,spin,orient,r=bead ? beadmax : T/2, l=H+extra_bot){
1215 up((H+extra_bot)/2){
1216 difference(){
1217 union(){
1218 thread_helix(d=T-.01, profile=profile, pitch = INCH/tpi, turns=twist/360, lead_in=taperlen, anchor=TOP);
1219 cylinder(d=T-depth*2,h=H,anchor=TOP);
1220 if (bead)
1221 down(bead_shift)
1222 rotate_extrude()
1223 polygon(beadpts);
1224 }
1225 up(.5)cyl(d=is_def(id) ? id : T-a-2*wall, l=H-extra_bot+1, anchor=TOP);
1226 }
1227 }
1228 children();
1229 }
1230}
1231
1232
1233
1234// Module: sp_cap()
1235// Synopsis: Creates an SPI threaded bottle cap.
1236// SynTags: Geom
1237// Topics: Bottles, Threading
1238// See Also: sp_neck()
1239// Usage:
1240// sp_cap(diam, type, wall, [style=], [top_adj=], [bot_adj=], [texture=], [$slop]) [ATTACHMENTS];
1241// Description:
1242// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
1243// supply the nominal outer diameter of the threads and the thread type, one of
1244// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
1245// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
1246// You can also choose between the L style thread, which is symmetric and
1247// the M style thread, which is an asymmetric buttress thread. Note that it
1248// is OK to mix styles, so you can put an L-style cap onto an M-style neck.
1249// .
1250// The 410 and 415 caps have very long unthreaded sections at the bottom.
1251// The bot_adj parameter specifies an amount to reduce that bottom extension, which might be
1252// necessary if the cap bottoms out on the bead. Be careful that you don't shrink past the threads,
1253// especially if making adjustments to 400 caps which have a very small bottom extension.
1254// These caps often contain a cardboard or foam sealer disk, which can be as much as 1mm thick, and
1255// would cause the cap to stop in a higher position.
1256// .
1257// You can also adjust the space between the top of the cap and the threads using top_adj. This
1258// will change how the threads engage when the cap is fully seated.
1259// .
1260// The inner diameter of the cap is set to allow 10% of the thread depth in clearance. The diameter
1261// is further increased by `2 * $slop` so you can increase clearance if necessary.
1262// .
1263// Note: there is a published SPI standard for necks, but absolutely nothing for caps. This
1264// cap module was designed based on the neck standard to mate reasonably well, but if you
1265// find ways that it does the wrong thing, file a report.
1266// Arguments:
1267// diam = nominal outer diameter of threads
1268// type = thread type, one of 400, 410 and 415
1269// wall = wall thickness
1270// ---
1271// style = Either "L" or "M" to specify the thread style. Default: "L"
1272// top_adj = Amount to reduce top space in the cap, which means it doesn't screw down as far. Default: 0
1273// bot_adj = Amount to reduce extension of cap at the bottom, which also means it doesn't screw down as far. Default: 0
1274// texture = texture for outside of cap, one of "knurled", "ribbed" or "none. Default: "none"
1275// $slop = Increase inner diameter by `2 * $slop`.
1276// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1277// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1278// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1279// Examples:
1280// sp_cap(48,400,2);
1281// sp_cap(22,400,2);
1282// sp_cap(22,410,2);
1283// sp_cap(28,415,1.5,style="M");
1284module sp_cap(diam,type,wall,style="L",top_adj=0, bot_adj=0, texture="none", anchor, spin, orient)
1285{
1286 table = struct_val(_sp_specs,type);
1287 dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
1288 entry = struct_val(table, diam);
1289 dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1290 assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
1291
1292 T = entry[0];
1293 I = entry[1];
1294 H = entry[2]-0.5;
1295 S = entry[3];
1296 tpi = entry[4];
1297 a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
1298
1299 twist = struct_val(_sp_twist, type);
1300
1301 echo(top_adj=top_adj,bot_adj=bot_adj);
1302 dum3=assert(top_adj<S+0.75*a, str("The top_adj value is too large so the thread won't fit. It must be smaller than ",S+0.75*a));
1303 oprofile = _sp_thread_profile(tpi,a,S+0.75*a-top_adj,style,flip=true);
1304 bounds=pointlist_bounds(oprofile);
1305 profile = fwd(-bounds[0].y,yflip(oprofile));
1306
1307 depth = a/2;
1308 taperlen = 2*a;
1309 assert(in_list(texture, ["none","knurled","ribbed"]));
1310 space=2*depth/10+2*get_slop();
1311 attachable(anchor,spin,orient,r= (T+space)/2+wall, l=H-bot_adj+wall){
1312 xrot(180)
1313 up((H-bot_adj)/2-wall/2){
1314 difference(){
1315 up(wall){
1316 if (texture=="knurled")
1317 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], tex_style="convex");
1318 else if (texture == "ribbed")
1319 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], tex_style="min_edge");
1320 else
1321 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8);
1322 }
1323 cyl(d=T+space, l=H-bot_adj+1, anchor=TOP);
1324 }
1325 thread_helix(d=T+space-.01, profile=profile, pitch = INCH/tpi, turns=twist/360, lead_in=taperlen, anchor=TOP, internal=true);
1326 }
1327 children();
1328 }
1329}
1330
1331
1332
1333// Function: sp_diameter()
1334// Synopsis: Returns the base diameter of an SPI bottle neck from the nominal diameter and type number.
1335// Topics: Bottles, Threading
1336// See Also: sp_neck(), sp_cap()
1337// Usage:
1338// true_diam = sp_diameter(diam,type)
1339// Description:
1340// Returns the actual base diameter (root of the threads) for a SPI plastic bottle neck given the nominal diameter and type number (400, 410, 415).
1341// Arguments:
1342// diam = nominal diameter
1343// type = closure type number (400, 410 or 415)
1344function sp_diameter(diam,type) =
1345 let(
1346 table = struct_val(_sp_specs,type)
1347 )
1348 assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415")
1349 let(
1350 entry = struct_val(table, diam)
1351 )
1352 assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1353 entry[0];
1354
1355
1356
1357// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap